package com.yonyou.crm.common.rest.handler;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;

import com.yonyou.crm.common.exception.CrmBusinessException;
import com.yonyou.crm.common.exception.CrmMessageException;

/**
 * 异常对象CrmRestError处理器
 *
 */
public class CrmDefaultRestErrorResolver implements RestErrorResolver, MessageSourceAware, InitializingBean{
	//_exmsg 表明 message 属性应该反射为运行时异常消息，例如 exception.getMessage()
	public static final String DEFAULT_EXCEPTION_MESSAGE_VALUE = "_exmsg";
	//_msg 只对 developerMessage 属性有效，它表明 developerMessage 的值和 message 的值一样。
    public static final String DEFAULT_MESSAGE_VALUE = "_msg";
    private static final Logger log = LoggerFactory.getLogger(CrmDefaultRestErrorResolver.class);
    private Map<String, CrmRestError> exceptionMappings = Collections.emptyMap();//异常对象CrmRestError的map
    private Map<String, String> exceptionMappingDefinitions = Collections.emptyMap();//异常配置信息的map
    private MessageSource messageSource;
    private LocaleResolver localeResolver;
    private String defaultMoreInfoUrl;
    private boolean defaultEmptyCodeToStatus;
    private String defaultDeveloperMessage;

    /**
     * 服务启动时，加载异常配置信息到map中
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        //代码创建几个默认的异常
        Map<String, String> definitions = createDefaultExceptionMappingDefinitions();

        //汇总：用户配置的异常+默认的异常
        if (this.exceptionMappingDefinitions != null && !this.exceptionMappingDefinitions.isEmpty()) {
            definitions.putAll(this.exceptionMappingDefinitions);
        }
        //根据汇总的异常map转为  <异常全名,CrmRestError>的map
        this.exceptionMappings = toRestErrors(definitions);
    }

    /**
     * 格式：<异常类名,"404,_exmsg">
     * @return
     */
    protected final Map<String,String> createDefaultExceptionMappingDefinitions() {
        Map<String,String> m = new LinkedHashMap<String, String>();
        
        // 400
        applyDef(m, HttpMessageNotReadableException.class, HttpStatus.BAD_REQUEST);
        applyDef(m, MissingServletRequestParameterException.class, HttpStatus.BAD_REQUEST);
        applyDef(m, TypeMismatchException.class, HttpStatus.BAD_REQUEST);
        applyDef(m, "javax.validation.ValidationException", HttpStatus.BAD_REQUEST);

        // 404
        applyDef(m, NoSuchRequestHandlingMethodException.class, HttpStatus.NOT_FOUND);
        applyDef(m, "org.hibernate.ObjectNotFoundException", HttpStatus.NOT_FOUND);

        // 405
        applyDef(m, HttpRequestMethodNotSupportedException.class, HttpStatus.METHOD_NOT_ALLOWED);

        // 406
        applyDef(m, HttpMediaTypeNotAcceptableException.class, HttpStatus.NOT_ACCEPTABLE);

        // 409
        applyDef(m, "org.springframework.dao.DataIntegrityViolationException", HttpStatus.CONFLICT);

        // 415
        applyDef(m, HttpMediaTypeNotSupportedException.class, HttpStatus.UNSUPPORTED_MEDIA_TYPE);

        return m;
    }

    private void applyDef(Map<String,String> m, Class clazz, HttpStatus status) {
        applyDef(m, clazz.getName(), status);
    }

    private void applyDef(Map<String,String> m, String key, HttpStatus status) {
        m.put(key, definitionFor(status));
    }

    private String definitionFor(HttpStatus status) {
        return status.value() + ",, " + DEFAULT_EXCEPTION_MESSAGE_VALUE;
    }
    // 异常信息转为异常CrmRestError对象
    @Override
    public CrmRestError resolveError(ServletWebRequest request, Object handler, Exception ex) {

    	CrmRestError template = getRestErrorTemplate(ex);//从初始化好的map中获取该异常对应的CrmRestError对象（但此时对象信息并不完整）
        if (template == null) {
            return null;
        }
        //补全CrmRestError对象信息
        CrmRestError.Builder builder = new CrmRestError.Builder();
        builder.setStatus(getStatusValue(template, request, ex));
        builder.setCode(getCode(template, request, ex));//code值如果没有配置，则默认和status的值相同
        builder.setMoreInfoUrl(getMoreInfoUrl(template, request, ex));
        builder.setThrowable(ex);

        String msg = getMessage(template, request, ex);//获取异常的message
        if (msg != null) {
            builder.setMessage(msg);
        }
        msg = getDeveloperMessage(template, request, ex);//获取异常的developerMessage
        if (msg != null) {
            builder.setDeveloperMessage(msg);
            log.error(msg);
        }

        return builder.build();//创建CrmRestError实例
    }

    protected int getStatusValue(CrmRestError template, ServletWebRequest request, Exception ex) {
        return template.getStatus().value();
    }

    protected String getCode(CrmRestError template, ServletWebRequest request, Exception ex) {
    	String code = template.getCode();
        if ( StringUtils.isEmpty(code) && defaultEmptyCodeToStatus) {
            code = getStatusValue(template, request, ex)+"";
        }
        if(ex.getClass()==CrmMessageException.class){
        	CrmMessageException cme = (CrmMessageException)ex;
        	String crmCode = cme.getCode();
        	if(crmCode!=null&&!"".equals(crmCode)){
        		return crmCode;
        	}
        }
        if(ex.getClass()==CrmBusinessException.class){
        	CrmBusinessException cme = (CrmBusinessException)ex;
        	String crmCode = cme.getCode();
        	if(crmCode!=null&&!"".equals(crmCode)){
        		return crmCode;
        	}
        }
        return code;
    }

    protected String getMoreInfoUrl(CrmRestError template, ServletWebRequest request, Exception ex) {
        String moreInfoUrl = template.getMoreInfoUrl();
        if (moreInfoUrl == null) {
            moreInfoUrl = this.defaultMoreInfoUrl;
        }
        return moreInfoUrl;
    }
    //端上显示信息
    protected String getMessage(CrmRestError template, ServletWebRequest request, Exception ex) {
    	return getMessage(template.getMessage(), request, ex);
    }
    //开发查看信息
    protected String getDeveloperMessage(CrmRestError template, ServletWebRequest request, Exception ex) {

    	String devMsg = "";
    	
    	String errMsg = ex.getMessage();
    	if (ex instanceof CrmMessageException || ex instanceof CrmBusinessException || (errMsg != null && errMsg.contains("Cause:"))) {
    		devMsg = errMsg;
    	}
		else {
			devMsg = getCustomMessage(ex);
		}
    			
    	return getMessage(devMsg, request, ex);
    }
    
    private String getCustomMessage(Exception ex) {
    	
    	StringBuffer devMsg = new StringBuffer();
    	
    	devMsg.append("\n");
    	devMsg.append("Cause:");
    	devMsg.append(ex.getMessage());
    	devMsg.append("\n");
    	devMsg.append("Exception：");
    	devMsg.append(ex.getClass());
    	if (ex.getStackTrace() != null && ex.getStackTrace().length > 0) {
    		devMsg.append("\n");
    		devMsg.append("Stacktrace：");
    		for (StackTraceElement ele : ex.getStackTrace()) {
    			devMsg.append(ele);
    			devMsg.append("\n");
    		}
    	}
    	
    	return devMsg.toString();
    }

    /**
     * 获取Exception的报错信息(如果值为_exmsg,则走特殊处理)
     */
    protected String getMessage(String msg, ServletWebRequest webRequest, Exception ex) {

        if (msg != null) {
            if (msg.equalsIgnoreCase("null") || msg.equalsIgnoreCase("off")) {
                return null;
            }
            if (msg.equalsIgnoreCase(DEFAULT_EXCEPTION_MESSAGE_VALUE)) {
                msg = ex.getMessage();
            }
            if (messageSource != null) {
                Locale locale = null;
                if (localeResolver != null) {
                    locale = localeResolver.resolveLocale(webRequest.getRequest());
                }
                msg = messageSource.getMessage(msg, null, msg, locale);
            }
        }

        return msg;
    }

    /**
     * 根据异常信息返回对应的Error对象
     */
    private CrmRestError getRestErrorTemplate(Exception ex) {
        Map<String, CrmRestError> mappings = this.exceptionMappings;
        if (CollectionUtils.isEmpty(mappings)) {
            return null;
        }
        CrmRestError template = null;
        String dominantMapping = null;
        int deepest = Integer.MAX_VALUE;
        for (Map.Entry<String, CrmRestError> entry : mappings.entrySet()) {
            String key = entry.getKey();
            int depth = getDepth(key, ex);
            if (depth >= 0 && depth < deepest) {
                deepest = depth;
                dominantMapping = key;
                template = entry.getValue();
            }
        }
        if (template != null && log.isDebugEnabled()) {
            log.debug("Resolving to CrmRestError template '" + template + "' for exception of type [" + ex.getClass().getName() +
                    "], based on exception mapping [" + dominantMapping + "]");
        }
        return template;
    }

    protected int getDepth(String exceptionMapping, Exception ex) {
        return getDepth(exceptionMapping, ex.getClass(), 0);
    }

    private int getDepth(String exceptionMapping, Class exceptionClass, int depth) {
        if (exceptionClass.getName().contains(exceptionMapping)) {
            return depth;
        }
        if (exceptionClass.equals(Throwable.class)) {
            return -1;
        }
        return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
    }


    /**
     * 异常配置信息 转为 ==>异常对象CrmRestError
     * @param smap
     * @return
     */
    protected Map<String, CrmRestError> toRestErrors(Map<String, String> smap) {
        if (CollectionUtils.isEmpty(smap)) {
            return Collections.emptyMap();
        }

        Map<String, CrmRestError> map = new LinkedHashMap<String, CrmRestError>(smap.size());

        for (Map.Entry<String, String> entry : smap.entrySet()) {
            String key = entry.getKey();//配置的异常类型
            String value = entry.getValue();//异常的value，如：400, _exmsg
            CrmRestError template = toRestError(value);//创建异常对应的CrmRestError对象
            map.put(key, template);
        }

        return map;
    }

    protected CrmRestError toRestError(String exceptionConfig) {
        String[] values = StringUtils.commaDelimitedListToStringArray(exceptionConfig);
        if (values == null || values.length == 0) {
            throw new IllegalStateException("Invalid config mapping.  Exception names must map to a string configuration.");
        }

        CrmRestError.Builder builder = new CrmRestError.Builder();

        boolean statusSet = false;
        boolean codeSet = false;
        boolean msgSet = false;
        boolean devMsgSet = false;
        boolean moreInfoSet = false;
        int i=0;
        for (String value : values) {

            String trimmedVal = StringUtils.trimWhitespace(value);

            String[] pair = StringUtils.split(trimmedVal, "=");
            if (pair != null) {//用=号配置时(复杂配置)
                String pairKey = StringUtils.trimWhitespace(pair[0]);
                if (!StringUtils.hasText(pairKey)) {
                    pairKey = null;
                }
                String pairValue = StringUtils.trimWhitespace(pair[1]);
                if (!StringUtils.hasText(pairValue)) {
                    pairValue = null;
                }
                if ("status".equalsIgnoreCase(pairKey)) {
                    int statusCode = getRequiredInt(pairKey, pairValue);
                    builder.setStatus(statusCode);
                    statusSet = true;
                } else if ("code".equalsIgnoreCase(pairKey)) {
//                	String code = getRequiredInt(pairKey, pairValue);
                    builder.setCode(pairValue);
                    codeSet = true;
                } else if ("msg".equalsIgnoreCase(pairKey)) {
                    builder.setMessage(pairValue);
                    msgSet = true;
                } else if ("devMsg".equalsIgnoreCase(pairKey)) {
                    builder.setDeveloperMessage(pairValue);
                    devMsgSet = true;
                } else if ("infoUrl".equalsIgnoreCase(pairKey)) {
                    builder.setMoreInfoUrl(pairValue);
                    moreInfoSet = true;
                }
            } else {//没用=号配置时(简单配置)
            	i++;
            	if (!statusSet && i==1) {
                    int val = getInt("status", trimmedVal);
                    if (val > 0) {
                        builder.setStatus(val);
                        statusSet = true;
                        continue;
                    }
                }
            	if (!codeSet && i==2) {
                  if (!StringUtils.isEmpty(trimmedVal)) {//code不为空时
                      builder.setCode(trimmedVal);
                      codeSet = true;
                      continue;
                  }
            	}
	        	 if (!msgSet && i==3) {
	                 builder.setMessage(trimmedVal);
	                 msgSet = true;
	                 continue;
	             }
	             if (!devMsgSet && i==4) {
	                 builder.setDeveloperMessage(trimmedVal);
	                 devMsgSet = true;
	                 continue;
	             }
            	
                /*int val;
                if (!statusSet) {
                    val = getInt("status", trimmedVal);
                    if (val > 0) {
                        builder.setStatus(val);
                        statusSet = true;
                        continue;
                    }
                }
                if (!codeSet) {
//                    val = getInt("code", trimmedVal);
                    if (!StringUtils.isEmpty(trimmedVal)) {//code不为空时
                        builder.setCode(trimmedVal);
                        codeSet = true;
                        continue;
                    }
                }
                if (!moreInfoSet && trimmedVal.toLowerCase().startsWith("http")) {
                    builder.setMoreInfoUrl(trimmedVal);
                    moreInfoSet = true;
                    continue;
                }
                if (!msgSet) {
                    builder.setMessage(trimmedVal);
                    msgSet = true;
                    continue;
                }
                if (!devMsgSet) {
                    builder.setDeveloperMessage(trimmedVal);
                    devMsgSet = true;
                    continue;
                }
                if (!moreInfoSet) {
                    builder.setMoreInfoUrl(trimmedVal);
                    moreInfoSet = true;
                    continue;
                }*/
            }
        }

        return builder.build();
    }

    private static int getRequiredInt(String key, String value) {
        try {
            int anInt = Integer.valueOf(value);
            return Math.max(-1, anInt);
        } catch (NumberFormatException e) {
            String msg = "Configuration element '" + key + "' requires an integer value.  The value " +
                    "specified: " + value;
            throw new IllegalArgumentException(msg, e);
        }
    }

    private static int getInt(String key, String value) {
        try {
            return getRequiredInt(key, value);
        } catch (IllegalArgumentException iae) {
            return 0;
        }
    }
    public CrmDefaultRestErrorResolver() {
        this.defaultEmptyCodeToStatus = true;
        this.defaultDeveloperMessage = DEFAULT_EXCEPTION_MESSAGE_VALUE;
    }

    public void setMessageSource(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    public void setLocaleResolver(LocaleResolver resolver) {
        this.localeResolver = resolver;
    }

    public void setExceptionMappingDefinitions(Map<String, String> exceptionMappingDefinitions) {
        this.exceptionMappingDefinitions = exceptionMappingDefinitions;
    }

    public void setDefaultMoreInfoUrl(String defaultMoreInfoUrl) {
        this.defaultMoreInfoUrl = defaultMoreInfoUrl;
    }

    public void setDefaultEmptyCodeToStatus(boolean defaultEmptyCodeToStatus) {
        this.defaultEmptyCodeToStatus = defaultEmptyCodeToStatus;
    }

    public void setDefaultDeveloperMessage(String defaultDeveloperMessage) {
        this.defaultDeveloperMessage = defaultDeveloperMessage;
    }
}
